package db

import (
	"encoding/json"
	"fmt"
	"os"
	"pd/server/com"
	"strconv"
	"strings"
	"sync"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/xorm"
	lru "github.com/hashicorp/golang-lru"
	"github.com/valyala/fasthttp"
)

var routerCache *lru.Cache

type ShardInfo struct {
	ShardID  int
	Host     string
	Port     int
	Username string
	Password string
	Dbname   string
	Charset  string
	Load     int
	Salt     string
}

var xormEngines map[string]*xorm.Engine
var xormEngineMutex sync.Mutex

type MysqlConn struct {
	Conn      *xorm.Engine
	ShardInfo *ShardInfo
	Table     string
	Mod       int
}

func NewMysqlConnByShardInfo(info *ShardInfo) (*MysqlConn, error) {
	xormEngineMutex.Lock()
	defer xormEngineMutex.Unlock()

	if xormEngines == nil {
		xormEngines = make(map[string]*xorm.Engine)
	}

	key := fmt.Sprintf("%s_%d_%s", info.Host, info.Port, info.Dbname)

	var err error
	if xormEngine, ok := xormEngines[key]; ok {
		return &MysqlConn{Conn: xormEngine, ShardInfo: info}, err
	} else {
		xormEngine, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", info.Username, info.Password, info.Host, info.Port, info.Dbname, info.Charset))

		if err != nil {
			return nil, err
		}

		// 设置连接池的空闲数大小
		xormEngine.SetMaxIdleConns(5)
		// 设置最大打开连接数
		xormEngine.SetMaxOpenConns(80)
		// 15分钟自动结束，防止被mysql主动关闭，造成invalid connection错误
		xormEngine.SetConnMaxLifetime(time.Minute * 15)

		// 读取环境变量XORM_SHOWSQL来设置是否开启日志，如果不显式关闭，则默认开启
		showSql := strings.ToLower(os.Getenv("XORM_SHOWSQL"))
		if showSql == "no" || showSql == "false" {
			xormEngine.ShowSQL(false)
		} else {
			// 开启日志
			xormEngine.ShowSQL(true)
			xormEngine.ShowExecTime(true)
		}

		xormEngines[key] = xormEngine

		return &MysqlConn{Conn: xormEngine, ShardInfo: info}, err
	}
}

func NewMysqlConnByMultiShardInfo(infos []ShardInfo) ([]MysqlConn, error) {
	var conns []MysqlConn
	for i := range infos {
		if conn, err := NewMysqlConnByShardInfo(&infos[i]); err != nil {
			continue
		} else {
			conns = append(conns, *conn)
		}
	}

	return conns, nil
}

func (this *MysqlConn) EachAssoc(query string, dest interface{}, cb func(err error)) error {
	rows, err := this.Conn.Rows(dest)
	if err != nil {
		return err
	}

	defer rows.Close()

	for rows.Next() {
		err = rows.Scan(dest)
		cb(err)
	}

	return err
}

func EachModTable(count int, cb func(mod int, table string) bool, table string) {
	EachMod(count, func(mod int) bool {
		do := cb(mod, strings.Replace(table, "{{mod}}", strconv.Itoa(mod), -1))

		// 如果表中不包含mod,则只处理一次
		if do && !strings.Contains(table, "{{mod}}") {
			return false
		}

		return do
	})
}

func EachMod(count int, cb func(mod int) bool) {
	for i := 1; i <= count; i++ {
		if !cb(i) {
			break
		}
	}
}

func getRouter(url string) ([]ShardInfo, error) {
	if routerCache == nil {
		routerCache, _ = lru.New(1000)
	}

	cacheKey := com.Crc32(url)
	if cacheVal, ok := routerCache.Get(cacheKey); ok {
		cacheItems := cacheVal.([]ShardInfo)
		return cacheItems, nil
	}

	statusCode, body, err := fasthttp.Get(nil, url)

	//fmt.Printf("%s \n", body)

	if statusCode != 200 || len(body) == 0 || err != nil {
		return nil, fmt.Errorf("can't find router form `%s`, err -> `%v`", url, err)
	}

	var shardInfos []ShardInfo

	if body[0] == '[' {
		json.Unmarshal(body, &shardInfos)
	} else {
		shardInfo := ShardInfo{}
		json.Unmarshal(body, &shardInfo)
		shardInfos = append(shardInfos, shardInfo)
	}

	decryptShardObject(shardInfos)

	routerCache.Add(cacheKey, shardInfos)

	return shardInfos, err
}

func decryptShardObject(infos []ShardInfo) {

	/// TODO::加解密占时禁用
	return
}

func GetLocalRootConn(ebayListingID int64) (*MysqlConn, error) {
	if info, err := GetLocalRootInfo(); err != nil {
		return nil, err
	} else {
		return NewMysqlConnByShardInfo(info)
	}
}

func GetPdMasterConn() (*MysqlConn, error) {
	if info, err := GetPdMasterInfo(); err != nil {
		return nil, err
	} else {
		return NewMysqlConnByShardInfo(info)
	}
}

// local root
func GetLocalRootInfo() (*ShardInfo, error) {
	localInfo := `{"shardID": 11111, 
		"host": "25402bae60f79b5e9a635711ab7aa122",
		"port": "61552",
		"username": "554f13bced197354301c7b06f8f7ea08",
		"password": "5bb335abdb2920d0e218388cd7d257f3",
		"dbname": "mysql",
		"charset": "utf8mb4_general_ci",
		"salt": "79f9070d9f6e4fa5"}`

	info := ShardInfo{}

	json.Unmarshal([]byte(localInfo), &info)

	return &info, nil
}

func GetPdMasterInfo() (*ShardInfo, error) {
	localInfo := `{"shardID": 3000, 
		"host": "db.t.jiutiandata.com",
		"port": 61552,
		"username": "jt_shard",
		"password": "jt@shard#9",
		"dbname": "pd",
		"charset": "utf8mb4",
		"salt": "79f9070d9f6e4fa5"}`

	info := ShardInfo{}

	json.Unmarshal([]byte(localInfo), &info)

	if com.IsLocal() {
		info.Host = "dev.t.jiutiandata.com"
	}

	return &info, nil
}
